COSA.Request = (function () {

    //region Private functions

    var _tryGetXrmValue = function ( value, type ) {
        return typeof value != 'undefined' ? value instanceof COSA.XrmValue ? value : new COSA.XrmValue(value) : null;
    };

    var _convertToKeyValueNodes = function ( parameters, serializationContext ) {
        var parametersNodes = [];
        for (var parameterName in parameters) {
            var keyValuePair = parameters[parameterName].toKeyValuePair(parameterName, serializationContext);
            parametersNodes.push(keyValuePair.serialize(serializationContext));
        }
        return parametersNodes;
    };

    //endregion Private functions

    //region Public functions

    //region RequestTemplate

    function RequestTemplate ( requestName, parameters ) {
        this.requestName = typeof requestName != 'undefined' ? requestName : null;
        this.parameters = typeof parameters != 'undefined' ? parameters : {};
    }

    RequestTemplate.prototype = {
        serialize: function ( serializationContext ) {
            var paramsAttrs = {},
                reqAttrs = {
                    'i:type': 'a:'.concat(this.requestName, 'Request')
                };
            if ( serializationContext.isSingle ) {
                reqAttrs['xmlns:a'] = 'http://schemas.microsoft.com/xrm/2011/Contracts';
                serializationContext.reserveLetter('a', 'http://schemas.microsoft.com/xrm/2011/Contracts');
                paramsAttrs['xmlns:b'] = 'http://schemas.datacontract.org/2004/07/System.Collections.Generic';
                serializationContext.reserveLetter('b', 'http://schemas.datacontract.org/2004/07/System.Collections.Generic');
            }

            return {
                tag: serializationContext.isSingle ? 'request' : 'c:OrganizationRequest',
                attr: reqAttrs,
                nodes: [{
                    tag: 'a:Parameters',
                    attr: paramsAttrs,
                    nodes: _convertToKeyValueNodes(this.parameters, serializationContext)
                }, {
                    tag: 'a:ReuqestId',
                    attr: {'i:nil': 'true'}
                }, {
                    tag: 'a:RequestName',
                    val: this.requestName
                }]
            };
        }
    };

    //endregion RequestTemplate

    //region ExtendedRequestTemplate

    function ExtendedRequestTemplate ( requestName, parameters ) {
        this.requestName = typeof requestName != 'undefined' ? requestName : null;
        this.parameters = typeof parameters != 'undefined' ? parameters : {};
    }

    ExtendedRequestTemplate.prototype = {
        serialize: function ( serializationContext ) {
            var rqAttrs = {},
                paramAttrs = {};

            if ( serializationContext.isSingle ) {
                rqAttrs['i:type'] = 'b:'.concat(this.requestName, 'Request');
                rqAttrs['xmlns:a'] = 'http://schemas.microsoft.com/xrm/2011/Contracts';
                serializationContext.reserveLetter('a', 'http://schemas.microsoft.com/xrm/2011/Contracts');
                rqAttrs['xmlns:b'] = 'http://schemas.microsoft.com/crm/2011/Contracts';
                serializationContext.reserveLetter('b', 'http://schemas.microsoft.com/crm/2011/Contracts');
                paramAttrs['xmlns:c'] = 'http://schemas.datacontract.org/2004/07/System.Collections.Generic';
                serializationContext.reserveLetter('c', 'http://schemas.datacontract.org/2004/07/System.Collections.Generic');
            } else {
                rqAttrs['i:type'] = 'd:'.concat(this.requestName, 'Request');
                rqAttrs['xmlns:d'] = 'http://schemas.microsoft.com/crm/2011/Contracts';
                serializationContext.reserveLetter('d', 'http://schemas.microsoft.com/crm/2011/Contracts');
            }

            return {
                tag: serializationContext.isSingle ? 'request' : 'c:OrganizationRequest',
                attr: rqAttrs,
                nodes: [{
                    tag: 'a:Parameters',
                    attr: paramAttrs,
                    nodes: _convertToKeyValueNodes(this.parameters, serializationContext)
                }, {
                    tag: 'a:RequestId',
                    attr: { 'i:nil':'true' }
                }, {
                    tag: 'a:RequestName',
                    val: this.requestName
                }]
            };
        }
    };



    //endregion ExtendedRequestTemplate

    //region RetrieveMultiple

    function RetrieveMultiple ( queryExpression ) {
        this.queryExpression = typeof queryExpression != 'undefined' && queryExpression instanceof COSA.QueryExpression ? queryExpression : null;
    }

    RetrieveMultiple.prototype = {
        serialize: function ( serializationContext ) {
            if ( this.queryExpression === null ) {
                throw new Error(COSA.Const.Error.REQUIRED_ATTRIBUTES_MISSED);
            }

            var request = new RequestTemplate(COSA.Const.Request.RetrieveMultiple, { 'Query':this.queryExpression });
            return request.serialize(serializationContext);
        }
    };

    //endregion RetrieveMultiple

    //region Retrieve

    function Retrieve ( logicalName, id, columnSet ) {
        this.id = typeof id != 'undefined' ? id : null;
        this.logicalName = typeof logicalName != 'undefined' ? logicalName : null;
        this.columnSet = typeof columnSet != 'undefined' ? columnSet : null;
    }

    Retrieve.prototype = {
        serialize: function ( serializationContext ) {
            var parameters = {
                'Target': new COSA.EntityReference(this.logicalName, this.id),
                'ColumnSet': this.columnSet
            };

            var request = new RequestTemplate(COSA.Const.Request.Retrieve, parameters);

            return request.serialize(serializationContext);
        }
    };

    //endregion Retrieve

    //region Create

    function Create ( entity ) {
        this.entity = typeof entity != 'undefined' ? entity : null;
    }

    Create.prototype = {
        serialize: function ( serializationContext ) {
            if ( this.entity === null || typeof serializationContext == 'undefined') {
                throw new Error(COSA.Const.Error.REQUIRED_ATTRIBUTES_MISSED);
            }

            var rq = new RequestTemplate(COSA.Const.Request.Create, { 'Target': this.entity });
            return rq.serialize( serializationContext );
        }
    };

    //endregion Create

    //region Update

    function Update ( entity ) {
        this.entity = typeof entity != 'undefined' && entity instanceof COSA.BusinessEntity ? entity : null;
    }

    Update.prototype = {
        serialize: function ( serializationContext ) {
            if ( this.entity === null || !( this.entity instanceof COSA.BusinessEntity ) ) return;
            var request = new RequestTemplate(COSA.Const.Request.Update, { 'Target': this.entity });
            return request.serialize(serializationContext);
        }
    };

    //endregion Update

    //region Delete

    function Delete ( logicalName, id ) {
        this.id = typeof id != 'undefined' ? id : null;
        this.logicalName = typeof logicalName != 'undefined' ? logicalName : null;
    }

    Delete.prototype = {
        serialize: function ( serializationContext ) {
            var parameters = {
                'Target': new COSA.EntityReference(this.logicalName, this.id)
            };
            var request = new RequestTemplate(COSA.Const.Request.Delete, parameters);
            return request.serialize(serializationContext);
        }
    };

    //endregion Delete

    //region ExecuteMultiple

    function ExecuteMultiple ( organizationRequestCollection, executeMultipleSettings ) {
        this.organizationRequestCollection = typeof organizationRequestCollection != 'undefined' ? organizationRequestCollection : null;
        this.settings = typeof executeMultipleSettings != 'undefined' ? executeMultipleSettings : null;
    }

    ExecuteMultiple.prototype = {
        serialize: function ( sc ) {
            sc.reserveLetter('a', 'http://schemas.microsoft.com/xrm/2011/Contracts');
            sc.reserveLetter('b', 'http://schemas.datacontract.org/2004/07/System.Collections.Generic');
            var parameters = [
                this.organizationRequestCollection.toKeyValuePair('Requests', sc),
                this.settings.toKeyValuePair('Settings')
            ];
            return {
                tag: 'request',
                attr: {
                    'i:type': 'a:ExecuteMultipleRequest',
                    'xmlns:a':'http://schemas.microsoft.com/xrm/2011/Contracts'
                },
                nodes: [{
                    tag: 'a:Parameters',
                    attr: {
                        'xmlns:b':'http://schemas.datacontract.org/2004/07/System.Collections.Generic'
                    },
                    nodes: parameters.map(function ( item ) { return item.serialize( sc ); })
                }, {
                    tag: 'a:ReuqestId',
                    attr: {'i:nil': 'true'}
                }, {
                    tag: 'a:RequestName',
                    val: 'ExecuteMultiple'
                }]
            };
        }
    };

    //endregion ExecuteMultiple

    //region RetrieveUserPrivileges

    function RetrieveUserPrivileges ( userId ) {
        this.userId = typeof userId != 'undefined' ? userId : null;
    }

    RetrieveUserPrivileges.prototype = {
        serialize: function ( serializationContext ) {
            var uId = typeof this.userId != typeof COSA.XrmValue ? new COSA.XrmValue(this.userId, COSA.Const.AttributeType.GUID) : this.userId;
            var request = new ExtendedRequestTemplate(COSA.Const.Request.RetrieveUserPrivileges, { 'UserId':uId });
            return request.serialize(serializationContext);
        }
    };

    //endregion RetrieveUserPrivileges

    //region RetrievePrincipalAccess

    function RetrievePrincipalAccess ( principal, target ) {
        this.principal = typeof principal != 'undefined' && principal instanceof COSA.EntityReference ? principal : null;
        this.target = typeof target != 'undefined' && target instanceof COSA.EntityReference ? target : null;
    }

    RetrievePrincipalAccess.prototype = {
        serialize: function ( serializationContext ) {
            var request = new ExtendedRequestTemplate(COSA.Const.Request.RetrievePrincipalAccess, { 'Principal': this.principal, 'Target': this.target });
            return request.serialize(serializationContext);
        }
    };

    //endregion RetrievePrincipalAccess

    //region RetrieveAttribute

    function RetrieveAttribute ( entityLogicalName, logicalName, retrieveAsIfPublished ) {
        this.entityLogicalName = _tryGetXrmValue(entityLogicalName);
        this.logicalName = _tryGetXrmValue(logicalName);
        this.retrieveAsIfPublished = _tryGetXrmValue(retrieveAsIfPublished);
    }

    RetrieveAttribute.prototype = {
        serialize: function ( serializationContext ) {
            var parameters = {
                'MetadataId': new COSA.XrmValue('00000000-0000-0000-0000-000000000000', COSA.Const.AttributeType.GUID),
                'EntityLogicalName': this.entityLogicalName,
                'LogicalName': this.logicalName,
                'RetrieveAsIfPublished': this.retrieveAsIfPublished
            };
            var request = new RequestTemplate(COSA.Const.Request.RetrieveAttribute, parameters);

            return request.serialize(serializationContext);
        }
    };

    //endregion RetrieveAttribute

    //region RetrieveEntity

    function RetrieveEntity ( logicalName, filters, retrieveAsIfPublished ) {
        this.logicalName = _tryGetXrmValue(logicalName);
        this.filters = _tryGetXrmValue(filters);
        this.retrieveAsIfPublished = _tryGetXrmValue(retrieveAsIfPublished);
    }

    RetrieveEntity.prototype = {
        serialize: function ( serializationContext ) {
            this.filters.type = COSA.Const.AttributeType.EntityFilters;
            var parameters = {
                'MetadataId': new COSA.XrmValue('00000000-0000-0000-0000-000000000000', COSA.Const.AttributeType.GUID),
                'LogicalName': this.logicalName,
                'EntityFilters': this.filters,
                'RetrieveAsIfPublished': this.retrieveAsIfPublished
            };
            var request = new RequestTemplate(COSA.Const.Request.RetrieveEntity, parameters);

            return request.serialize(serializationContext);
        }
    };

    //endregion RetrieveEntity

    //region unrefactored

    function CustomRequestTemplate(rqName, parameters, isSingle) {
        var paramsAttr = {},
            reqAttrs = {};

        if (isSingle) {
            reqAttrs['xmlns:a'] = COSA.Request._resolveNamespace('a');
            paramsAttr['xmlns:b'] = COSA.Request._resolveNamespace('b');
        }

        return {
            tag: isSingle ? 'request' : 'c:OrganizationRequest',
            attr: reqAttrs,
            nodes: [{
                tag: 'a:Parameters',
                attr: paramsAttr,
                nodes: parameters
            }, {
                tag: 'a:ReuqestId',
                attr: {'i:nil': 'true'}
            }, {
                tag: 'a:RequestName',
                val: rqName
            }]
        };
    }

    function OrganizationRequest( requestName, parameters, isSingle ) {
        return new CustomRequestTemplate( requestName, parameters, isSingle );
    }

    //endregion unrefactored

    //endregion Public functions

    //region SetState

    function SetState( entityMoniker, stateCodeValue, statusCodeValue ) {
        this.entityMoniker = typeof entityMoniker != 'undefined' && entityMoniker instanceof COSA.EntityReference ? entityMoniker : null;
        this.stateCodeValue = typeof stateCodeValue != 'undefined' ? stateCodeValue : null;
        this.statusCodeValue = typeof statusCodeValue != 'undefined' ? statusCodeValue : null;
    }

    SetState.prototype = {
        serialize: function ( serializationContext ) {
            var parameters = {
                EntityMoniker: this.entityMoniker,
                State: new COSA.OptionSetValue(this.stateCodeValue),
                Status: new COSA.OptionSetValue(this.statusCodeValue)
            };

            var rq = new ExtendedRequestTemplate(COSA.Const.Request.SetState, parameters);
            return rq.serialize( serializationContext );
        }
    };

    //endregion SetState

    return {
        OrganizationRequest: OrganizationRequest,
        Create: Create,
        Retrieve: Retrieve,
        RetrieveMultiple: RetrieveMultiple,
        Update: Update,
        Delete: Delete,
        ExecuteMultiple: ExecuteMultiple,
        RetrieveUserPrivileges: RetrieveUserPrivileges,
        RetrievePrincipalAccess: RetrievePrincipalAccess,
        RetrieveAttribute: RetrieveAttribute,
        RetrieveEntity: RetrieveEntity,
        SetState: SetState
    };
})();